package com.yunzheng.util;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yunzheng.entity.*;
import com.yunzheng.enums.ResultStatuesEnums;
import com.yunzheng.exception.ServiceException;
import com.yunzheng.mapper.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author: 郭思良
 * @date: 2020/2/8 18:25
 */
@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Resource
    private SysUserMapper sysUserMapper;

    @Resource
    private SysAuthMapper sysAuthMapper;

    @Resource
    private SysRoleMapper sysRoleMapper;

    @Resource
    private SysPermMapper sysPermMapper;

    @Resource
    private SysRolePermMapper sysRolePermMapper;

    @Resource
    private SysUserRoleMapper sysUserRoleMapper;



    /**
     * 进行权限管理、授权验证
     *
     *
     * @param principals shiro封装的
     * @return 返回带有角色信息的info
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        // user里面存入了当前Subject的权限和角色
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        ActivityUser user = new ActivityUser();
        if (principals != null){
            user = (ActivityUser) principals.getPrimaryPrincipal();
        }

        // 加入所有的角色(id)和权限(url)
        Set<String> perms = user.getPermissions().stream().map(SysPerm::getUrl).collect(Collectors.toSet());
        Set<String> roles = user.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toSet());
        simpleAuthorizationInfo.addStringPermissions(perms);
        simpleAuthorizationInfo.addRoles(roles);
        return simpleAuthorizationInfo;
    }



    /**
     * 进行的登录管理、登录认证
     *
     *
     * @param token shiro封装的UserPasswordToken
     * @return 返回shiro封装的info，存入了当前用户的信息
     * @throws AuthenticationException 在登录认证的过程中出现了密码错误、用户名错误等异常会在login方法中抛出
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //通过token.getPrincipal方法得到Login方法时传入UsernamePasswordToken中的username-->这里是mobile
        String currentMobile = (String) token.getPrincipal();
        //判断用户是否不存在,返回的是SysAuth的对象
        SysAuth user = ensureUserExist(currentMobile);
        //判断用户是否被锁定，返回的是AdminUser的对象
        SysUser sysUser = ensureUserLocked(currentMobile);
        //在登录时获得其角色和权限
        ActivityUser activityUser = selectRolesPerms(sysUser);


        /*
         *
         * 这个方法是用来做密码匹配的
         *
         * 这个类的构造方法有参数3个的还有参数4个的。
         * 首先第一个参数可以为任意类型的任意值，通常是getPrincipal方法得到的用户名
         *     由于授权认证时需要多次执行授权认证流程，所以将用户、角色、权限封装成对象直接传过去
         *     就避免了多次访问doGetAuthorizationInfo方法
         * 第二个参数使用 查询到的用户密码，而不是前台传来的密码
         * 第三个（salt） 是加盐加密时使用的盐值 ByteSource类型的
         * 第四个参数： this.getName()方法,该类的类名
         */
        return new SimpleAuthenticationInfo(activityUser, user.getPasswd(),this.getName());

    }

    /**
     * 判断用户是否被禁用
     * @param currentMobile 当前subject的手机号
     * @return 返回数据库查出的用户信息
     */
    private SysUser ensureUserLocked(String currentMobile) {
        SysUser adminUser = sysUserMapper.selectOne(new QueryWrapper<SysUser>().eq(SysUser.COL_USER_PHONE, currentMobile));
        // 判断用户是否被锁定或被冻结
        if (adminUser.getUserStatus() != 0){
            throw new ServiceException(ResultStatuesEnums.USER_LOCKED);
        }
        return adminUser;
    }

    /**
     * 判断用户是否存在
     * @param currentMobile 当前subject的手机号
     * @return 返回数据库查出用于用户验证的对象
     */
    private SysAuth ensureUserExist(String currentMobile) {
        //Shiro认证流程是首先获得用户名，查询数据库获得实体User类，在做密码匹配
        SysAuth user = sysAuthMapper.selectOne(new QueryWrapper<SysAuth>().eq(SysAuth.COL_USER_PHONE, currentMobile));
        if (user == null) {
            throw new ServiceException(ResultStatuesEnums.USER_NOT_EXIST);
        }
        return user;
    }

    private ActivityUser selectRolesPerms(SysUser sysUser) {
        //在登录时获取权限，就没有必要使用AOP来进行获取权限了，这样可以减少查询数据库的次数
        List<SysPerm> perms = new ArrayList<>();
        List<SysRole> roles = new ArrayList<>();
        //如果用户身份是系统管理员，就给所有的权限的角色
        if (sysUser.getUserIdentify() == 2){
            perms = sysPermMapper.selectList(new QueryWrapper<>());
            roles = sysRoleMapper.selectList(new QueryWrapper<>());
        } else {
            //获取角色
            List<SysUserRole> userRoles = sysUserRoleMapper.selectList(new QueryWrapper<SysUserRole>().eq(SysUserRole.COL_USER_ID, sysUser.getUserId()));
            List<String> roleIds = userRoles.stream().map(SysUserRole::getRoleId).collect(Collectors.toList());
            if (roleIds.size() > 0){
                roles = sysRoleMapper.selectBatchIds(roleIds);
                //获取权限
                List<SysRolePerm> rolePerms = sysRolePermMapper.selectList(new QueryWrapper<SysRolePerm>().in(SysRolePerm.COL_ROLE_ID, roleIds));
                Set<String> permIds = rolePerms.stream().map(SysRolePerm::getPermId).collect(Collectors.toSet());
                perms = new ArrayList<>();
                if (permIds.size() > 0){
                    perms = sysPermMapper.selectBatchIds(permIds);
                }
            }

        }
        return new ActivityUser(sysUser, roles, perms);
    }
}
